Skip to main content

Result & Option

Option<T> — Handling “maybe a value”

enum Option<T> {
Some(T),
None,
}

Use Option<T> when:

  • A value might be present.
  • Absence is normal and not an error (e.g., searching, optional fields).

Example 1: Finding a value in a list

fn find_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}

fn main() {
let nums = vec![1, 3, 5, 8, 9];

match find_even(&nums) {
Some(value) => println!("Found even number: {}", value),
None => println!("No even number found"),
}
}
  • Some(value) → success.
  • None → nothing found.
  • The compiler forces you to handle both cases.

Common Option methods

unwrap() (may panic)

let x = Some(10);
println!("{}", x.unwrap()); // prints 10

unwrap_or()

let x: Option<i32> = None;
println!("{}", x.unwrap_or(0)); // prints 0

if let shorthand

if let Some(value) = find_even(&nums) {
println!("Found: {}", value);
}

Result<T, E> — Handling recoverable errors

enum Result<T, E> {
Ok(T),
Err(E),
}

Use Result<T, E> when:

  • An operation might fail.
  • You want to explain why it failed.

Example 2: Safe division

fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}

fn main() {
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
  • Ok(result) → success.
  • Err(error_message) → failure with reason.

Example 3: Reading a file

use std::fs::File;
use std::io::{self, Read};

fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?; // may fail
let mut contents = String::new();
file.read_to_string(&mut contents)?; // may fail
Ok(contents)
}

fn main() {
match read_file("hello.txt") {
Ok(text) => println!("File contents:\n{}", text),
Err(e) => println!("Failed to read file: {}", e),
}
}
  • ? propagates errors automatically.
  • The function returns Result<String, io::Error>.

Converting between Option and Result

OptionResult

let value: Option<i32> = None;
let result = value.ok_or("Value was missing");

ResultOption

let result: Result<i32, &str> = Ok(5);
let option = result.ok();

Option vs Result — When to use which

Use Option<T> when…Use Result<T, E> when…
Value may or may not existOperation may fail
Absence is normalYou need an error reason
No error details neededError details matter

The ? operator (works with both!)

With Result:

fn get_number() -> Result<i32, String> {
let n = divide(10, 2)?; // if Err, returns early
Ok(n * 2)
}

With Option:

fn double_first_even(nums: &[i32]) -> Option<i32> {
let even = find_even(nums)?; // if None, returns None
Some(even * 2)
}

Summary

  • Option<T> = “There might be a value.”
  • Result<T, E> = “This might fail, and here’s why.”
  • Both force you to handle errors at compile time.
  • Use match, if let, unwrap_or, or ? to handle them cleanly.
  • Prefer Result over panic! for recoverable errors.